#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/unistd.h>
#include <linux/sched.h>
#include <linux/list.h>
#include <asm/uaccess.h>

#define PATH_OK        1
#define PATH_INCORRECT 0
#define SWAPPER_PID    0
#define INIT_PID       1

int checkpath(int *array, int size)
{
    int i;
    int ret_val = PATH_OK;
    struct task_struct *parent = NULL, *child;

    // check input.
    if(size <= 0 || array == NULL) {
        //printk("[K] do_fork_n: ***ERROR*** size < 0 or array is NULL\n");
        ret_val = -EINVAL;
        goto checkpath_out;
    }
    
    // check read access.
    if(!access_ok(VERIFY_READ, array, size)) {
        //printk("[K] do_fork_n: ***ERROR*** can't read from array\n");
        ret_val = -EINVAL;
        goto checkpath_out;
    }

    // go over the array and check each entry.
    for (i = 0; i < size; ++i) {        
        int pid;
        
        // copy data to kernel array.
        if (copy_from_user(&pid, array + i, sizeof(int)) != 0) {
            //printk("[K] do_fork_n: ***ERROR*** failed to copy from user buffer\n");
            ret_val = -EINVAL;
            goto checkpath_out;
        }
        
        // handle special case - swapper pid on first element
        if (pid == SWAPPER_PID && i == 0)
            continue;

        // find the task.
        if (!(child = find_task_by_pid(pid))) {
            //printk("[K] do_fork_n: task #%d (pid=%d) was not found\n", i+1, pid);
            ret_val = PATH_INCORRECT;
            break;
        }
                
        if (i > 0) { // check parent (only after it's set, when i > 0).
            if (child->p_opptr != parent) { // check if the prev. task is the parent of the child task.
                // we don't set swapper task struct (only when it's first in array)
                // and we should check it here.
                if (pid == INIT_PID && !parent) {
                    continue;
                }
                
                //printk("[K] do_fork_n: task #%d with pid %d is not child of pid %d\n", i+1,
                //    child->pid, parent ? parent->pid : 0);
                    
                ret_val = PATH_INCORRECT;
                break;
            }
        }
        
        parent = child;
    }
    
checkpath_out:
    return ret_val;
}

int sys_checkpath(int *array, int size)
{
    return checkpath(array, size);
}
